#include "timerwheelscheduler.h"

static uint32_t sIncId = 1;

TimerWheelScheduler::TimerWheelScheduler(uint32_t timer_step_ms)
    : m_stop(false)
    , m_timer_step_ms(timer_step_ms)
{

}

bool TimerWheelScheduler::start()
{
    if (m_timer_step_ms < 50)
    {
        return false;
    }

    if (m_time_wheels.empty())
    {
        return false;
    }

    m_thread = std::thread(std::bind(&TimerWheelScheduler::run, this));

    return true;
}

void TimerWheelScheduler::run()
{
    while(true)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(m_timer_step_ms));

        std::lock_guard<std::mutex> lock(m_mutex);
        if (m_stop)
        {
            break;
        }
    }

    TimeWheelPtr leastTimeWheel = getLeastTimeWheel();
    leastTimeWheel->increase();

    std::list<TimerPtr> slot = leastTimeWheel->getAndClearCurrentSlot();
    for (const TimerPtr& pTimer : slot)
    {
        auto itr = m_cancel_timer_ids.find(pTimer->getId());
        if (itr != m_cancel_timer_ids.end())
        {
            m_cancel_timer_ids.erase(itr);
            continue;
        }

        pTimer->run();
        if (pTimer->getRepeated())
        {
            pTimer->updateWhenTime();
            getGreatestTimeWheel()->addTime(pTimer);
        }
    }
}

void TimerWheelScheduler::stop()
{
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_stop = true;
    }

    m_thread.join();
}

TimeWheelPtr TimerWheelScheduler::getLeastTimeWheel()
{
    if (m_time_wheels.empty())
    {
        return TimeWheelPtr();
    }

    return m_time_wheels.back();
}

TimeWheelPtr TimerWheelScheduler::getGreatestTimeWheel()
{
    if (m_time_wheels.empty())
    {
        return TimeWheelPtr();
    }

    return m_time_wheels.front();
}

void TimerWheelScheduler::appendTimeWheel(uint32_t scales, uint32_t scale_unit_ms, const std::string &name)
{
    TimeWheelPtr pTimeWheel = std::make_shared<TimeWheel>(scales, scale_unit_ms, name);
    if (m_time_wheels.empty())
    {
        m_time_wheels.push_back(pTimeWheel);
        return;
    }

    TimeWheelPtr pGreaterTimeWheel = m_time_wheels.back();
    pGreaterTimeWheel->setLessLevelTw(pTimeWheel.get());
    pTimeWheel->setGreaterLevelTw(pGreaterTimeWheel.get());
    m_time_wheels.push_back(pTimeWheel);
}

uint32_t TimerWheelScheduler::createTimerAt(int64_t when_ms, const TimerTask &handler)
{
    if (m_time_wheels.empty())
    {
        return 0;
    }

    std::lock_guard<std::mutex> lock(m_mutex);
    ++sIncId;
    getGreatestTimeWheel()->addTime(std::make_shared<Timer>(sIncId, when_ms, 0, handler));
    return sIncId;
}

uint32_t TimerWheelScheduler::createTimerAfter(int64_t delay_ms, const TimerTask &handler)
{
    int64_t when = getNowTimeStamp() + delay_ms;
    return createTimerAt(when, handler);
}

uint32_t TimerWheelScheduler::createTimerEvery(int64_t interval_ms, const TimerTask &handler)
{
    if (m_time_wheels.empty())
    {
        return 0;
    }

    std::lock_guard<std::mutex> lock(m_mutex);
    ++sIncId;
    int64_t when = getNowTimeStamp() + interval_ms;
    getGreatestTimeWheel()->addTime(std::make_shared<Timer>(sIncId, when, interval_ms, handler));
    return sIncId;
}

void TimerWheelScheduler::cancelTimer(uint32_t timer_id)
{
    std::lock_guard<std::mutex> lock(m_mutex);
    m_cancel_timer_ids.insert(timer_id);
}
